﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;

namespace WorldOfMUDra
{
    /// <summary>
    /// Areas are essentially what the world is made of. They hold players and contain descriptions of their contents 
    /// loaded from xml files. Using the LinkWith() method, you can easily make a network of areas, thereby creating 
    /// a world.
    /// </summary>
    public class Area
    {
        readonly string _name;
        readonly string _id;
        string _description;
        readonly string _virginmessage;

        readonly Dictionary<string, Area> _portals = new Dictionary<string, Area>();
        readonly List<Player> _occupants = new List<Player>();
        readonly Dictionary<string,IItem> _items = new Dictionary<string,IItem>();
        readonly List<AreaEvent> _onFirstVisitEvents = new List<AreaEvent>();
        protected Dictionary<string, string> Tags;

        /// <summary>
        /// Constructor for Area class that takes in the name of file containing data about the area.
        /// </summary>
        /// <param name="filename">Name of file to parse for data. XML file extensions will be detected and parsed differently than text files. XML is prefered.</param>
        public Area(string filename)
        {
            Tags = new Dictionary<string, string>();
            Console.WriteLine("Loading "+filename+" as an Area...");
            if (filename.Split('.')[1].ToLower() == "xml")
            {
                Console.WriteLine("XML file extension detected. Will attempt to parse as XML document...");
                XmlTextReader reader = new XmlTextReader(filename);
                while (!reader.EOF)
                {
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        switch (reader.Name.ToLower())
                        {
                            case "description":
                                _description = Globals.RemoveExtraSpaces(reader.ReadString().Trim());
                                //Console.WriteLine("Added description '" + this.description + "'");
                                break;
                            case "name":
                                _name = reader.ReadString().Trim();
                                Console.WriteLine("Added name '" + _name + "'");
                                break;
                            case "id":
                                _id = reader.ReadString().Trim();
                                Console.WriteLine("Added id '" + _id + "'");
                                break;
                            case "virginmessage":
                                _virginmessage = Globals.RemoveExtraSpaces(reader.ReadString().Trim());
                                //Console.WriteLine("Added V.M. '" + this.virginmessage + "'");
                                break;
                            default:
                                Tags.Add(reader.Name.ToLower(), reader.ReadString().Trim());
                                break;
                        }
                    }
                    else { reader.Read(); }
                }
                reader.Close();
            }
            else
            {
                try
                {
                    string[] lines = File.ReadAllLines(filename);
                    if (lines.Length >= 4)
                    {
                        _name = lines[0].Trim();
                        _id = lines[1].Trim();
                        _virginmessage = lines[2].Trim();
                        string desc = "";
                        for (int i = 3; i < lines.Length; i++)
                        {
                            desc += (lines[i].TrimEnd() + ' ');
                        }
                        _description = desc.Trim();
                    }
                    else
                    {
                        throw new Exception();
                    }
                }
                catch (Exception)
                {
                    Console.WriteLine("Issues loading area config file " + filename);
                }
            }
            Console.WriteLine(filename + " loaded.");
            Console.WriteLine();
            World.Areas.Add(_id, this);
        }

        /// <summary>
        /// Lazy constructor for Area that takes only a name and a description.
        /// </summary>
        /// <param name="name">The name of the area.</param>
        /// <param name="description">The description of the area.</param>
        public Area(string name, string description)
        {
            Tags = new Dictionary<string, string>();
            _name = name;
            _description = description;
            World.Areas.Add(_id, this);
        }

        public string Name
        {
            get { return (_name + " (" + _id + ")"); }
        }

        public string Id
        {
            get { return _id; }
        }

        public string Description
        {
            get 
            {
                string places = _portals.Values.Aggregate("", (current, place) => current + (" " + place.Name + ","));

                string fullDesc = _description;
                if (fullDesc == null) throw new ArgumentNullException("fullDesc");
                if (!places.Equals(""))
                {
                    fullDesc += " You see paths to" + places.TrimEnd(',') + ".";
                }
                return fullDesc; 
            }
            set { _description = value; }
        }

        public string VirginMessage
        {
            get 
            {
                return _virginmessage != "[none]" ? _virginmessage : null;
            }
        }

        public void LinkWith(Area area)
        {
            if (!_portals.ContainsKey(area.Id))
            {
                _portals.Add(area.Id, area);
            }
        }

        public bool LeadsTo(string portalId)
        {
            if (portalId == null) throw new ArgumentNullException("portalId");
            return _portals.ContainsKey(portalId.Trim());
        }

        public void RemovePlayer(Player p)
        {
            _occupants.Remove(p);
        }

        public void OnFirstVisit(Player visitor)
        {
            foreach (AreaEvent eve in _onFirstVisitEvents)
            {
                eve(visitor);
            }
        }

        public void AddOnFirstVisitEvent(AreaEvent Event)
        {
            _onFirstVisitEvents.Add(Event);
        }

        public void MoveHere(Player p)
        {
            if (p.CurrentArea() != null) p.CurrentArea().RemovePlayer(p);
            p.MoveTo(this);
            _occupants.Add(p);
            if (!p.HasVisited(this))
            {
                p.SendWorldMessage(VirginMessage);
                OnFirstVisit(p);
            }
            p.AddVisitedArea(_id);
        }

        public void ChatToOccupants(string username, string msg)
        {
            foreach (Player occupant in _occupants)
            {
                occupant.SendChatMessage(username, msg);
            }
        }

        public void WorldMessageToOccupants(string msg)
        {
            if (msg == null) throw new ArgumentNullException("msg");
            foreach (Player occupant in _occupants)
            {
                occupant.SendWorldMessage(msg);
            }
        }

        public Area Places(string idOf)
        {
            if (idOf == null) throw new ArgumentNullException("idOf");
            return _portals[idOf];
        }

        public bool ContainsItem(string itemId)
        {
            if (itemId == null) throw new ArgumentNullException("itemId");
            return _items.ContainsKey(itemId);
        }

        public void AddItem(IItem item)
        {
            if (item == null) throw new ArgumentNullException("item");
            _items.Add(item.ID, item);
        }

        public IItem GetItem(string itemId)
        {
            if (itemId == null) throw new ArgumentNullException("itemId");
            return _items.ContainsKey(itemId) ? _items[itemId] : null;
        }

        public string ItemList
        {
            get
            {
                if (_items.Count == 0) { return "nothing"; }
                string r = _items.Values.Aggregate("", (current, item) => current + (item.Name + ", "));
                return r.TrimEnd(' ', ',');
            }
        }
    }
}
